﻿using System;
using System.Collections.Generic;
using iTextSharp.text;
using iTextSharp.text.pdf;
using System.Text;
using System.Linq;
using System.IO;
using Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Meta;
using Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Configuration;

namespace Ch.Admin.Bfs.Sms.Sdmx.Extension.CodeBook.Pdf
{
    public class BookGenerator
    {

        public static int TOC;

        #region Internal Members

        /// <summary>ITextSharp objects for Pdf document generation </summary>
        private PdfWriter docWriter;
        private Document doc;
        public PdfFonts Fonts;
        private string lg;
        private bool hasImpressum;
        private IMetaProviderFactory metaProviderFactory;
        private BookConfiguration bookConfig;
        List<TableOfContentsEntry> contentsTable;
        public static bool InTOC;
        #endregion

        #region Constructor

        public BookGenerator(string fontDirectory, IMetaProviderFactory metaFactory)
        {
            // Embedding font 
            string fontDir = fontDirectory;

            Fonts = new PdfFonts(fontDir);
            metaProviderFactory = metaFactory;
        }
        #endregion

        public void Generate(string pdfPath, BookHierarchy bookHierarchy, string lang, BookConfiguration cfg, System.Drawing.Image logo)
        {
            lg = lang;
            bookConfig = cfg;
            bookConfig.LanguageId = lang;

            // instanciate document
            doc = new Document(PageSize.A4, bookConfig.Margins.Left, bookConfig.Margins.Right,
                bookConfig.Margins.Top, bookConfig.Margins.Bottom);

            try
            {
                // Clear history of used codelist
                metaProviderFactory.ClearReferedCodelist();

                // Instantiate Pdf doc writer
                docWriter = PdfWriter.GetInstance(doc, new FileStream(pdfPath, FileMode.Create));
                docWriter.SetLinearPageMode();

                // Create a footer helper and record event
                contentsTable = new List<TableOfContentsEntry>();
                PDFFooterHelper footerHelper = new PDFFooterHelper(bookConfig.CoverTitle[lang], Fonts, bookConfig);
           //     PDFFooterHelper footerHelper = new PDFFooterHelper("SMS Codebook generator - © BFS/OFS 2013", Fonts, bookConfig);
                docWriter.PageEvent = footerHelper;

                doc.Open();

                // Generate CoverPage
                GenerateCover(logo);

                foreach (HierarchyNode nodeModuleType in bookHierarchy.Nodes)
                {
                    if ((TableOfContents)((int)nodeModuleType.Tag) != TableOfContents.Codelists)
                        GenerateModulesOfContent((TableOfContents)((int)nodeModuleType.Tag), nodeModuleType.Nodes);
                }

                // Now detected codelist (if requested)
                HierarchyNode nodeReferedCodelists = SpecificMergeCodelists(bookHierarchy);

                // and append specific if any

                if (nodeReferedCodelists.Nodes.Count > 0)
                {
                    // Generate a common title for codelists
                    BaseModule bm = new BaseModule(null, doc, Fonts, bookConfig);
                    PdfPTable table = bm.TableTitle(bookConfig.TitleModuleCodelist[lg]);
                    // Add toc
                    AddToc(table);
                    doc.Add(table);
                    doc.Add(new Paragraph("\n"));

                    GenerateModulesOfContent(TableOfContents.Codelists, nodeReferedCodelists.Nodes);
                }
                InTOC = true;
                CreateTableOfContents();
                InTOC = false;

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                try
                {
                    if (doc != null && doc.IsOpen())
                        doc.Close();
                }
                catch { }
            }
        }

        private void GenerateCover(System.Drawing.Image logo)
        {
            BaseModule bm = new BaseModule(null, doc, Fonts, bookConfig);

            PdfPTable table = new PdfPTable(1);
            table.TotalWidth = bm.TotalWidth;

            Phrase ph = new Phrase("Département fédéral de l'intérieur DFI", Fonts.FontCoverHead);
            PdfPCell cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            ph = new Phrase("Office fédéral de la statistique OFS", Fonts.FontCoverHeadOfs);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            ph = new Phrase("Division Etudes démographiques et enquêtes auprès des ménages", Fonts.FontCoverHead);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            table.WriteSelectedRows(0, -1, bm.TotalWidth / 2 + 34, doc.PageSize.Height - doc.TopMargin - 10, docWriter.DirectContent);


            iTextSharp.text.Image jpg = iTextSharp.text.Image.GetInstance(logo, System.Drawing.Imaging.ImageFormat.Jpeg);

            //Resize image depend upon your need
            jpg.ScaleToFit(160f, 120f);

            //Give space before image
            jpg.SpacingBefore = 80f;

            //Give some space after the image
            jpg.SpacingAfter = 1f;
            // jpg.Alignment = Element.ALIGN_LEFT;

            doc.Add(jpg);
            doc.Add(new Paragraph("\n"));
            doc.Add(new Paragraph("\n"));
            doc.Add(new Paragraph("\n"));
            doc.Add(new Paragraph("\n"));

            // Cover title
            table = new PdfPTable(1);
            table.TotalWidth = bm.TotalWidth;

            ph = new Phrase(bookConfig.CoverTitle[bookConfig.LanguageId], Fonts.FontCoverTitle);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.BOTTOM_BORDER | Rectangle.TOP_BORDER;
            cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            cell.MinimumHeight = 100f;

            table.AddCell(cell);
            doc.Add(table);

            // Codebook sub title
            table = new PdfPTable(1);
            table.TotalWidth = bm.TotalWidth;

            ph = new Phrase("Codebook", Fonts.FontCoverSubTitle);
            cell = new PdfPCell(ph);
            cell.Border = Rectangle.NO_BORDER;
            cell.VerticalAlignment = Element.ALIGN_MIDDLE;
            cell.MinimumHeight = 40f;

            table.AddCell(cell);
            doc.Add(table);


            doc.NewPage();
        }

        /// <summary>
        /// Merge detected codelist with user specific list that could be provided
        /// </summary>
        /// <param name="bookHierarchy">List of all artefacts specified, grouped by domain type</param>
        /// <returns>a Node with subNodes holding codelist id's</returns>
        private HierarchyNode SpecificMergeCodelists(BookHierarchy bookHierarchy)
        {
            // Detected codelist (if requested)
            HierarchyNode nodeReferedCodelists = metaProviderFactory.ReferedCodelistModuleContent();

            // Now add specifically selected if not yet part of list
            foreach (HierarchyNode nodeModuleType in bookHierarchy.Nodes)
            {
                // Select codelist domain
                if ((TableOfContents)((int)nodeModuleType.Tag) == TableOfContents.Codelists)
                {
                    foreach (HierarchyNode clNode in nodeModuleType.Nodes)
                    {
                        HierarchyNode nd = nodeReferedCodelists.Nodes.Find(x => x.Id == clNode.Id);
                        if (nd == null)
                        {
                            nodeReferedCodelists.Nodes.Add(new HierarchyNode(clNode.Id, string.Empty, null));
                        }
                    }
                }
            }
            return nodeReferedCodelists;

        }

        private void GenerateModulesOfContent(TableOfContents moduleType, List<HierarchyNode> fullIdList)
        {
            foreach (HierarchyNode nodeModule in fullIdList)
                GenerateModule(moduleType, nodeModule.Id);
        }

        private void GenerateModule(TableOfContents moduleType, string metafullId)
        {
            IMetaProvider metaProvider = metaProviderFactory.GetMetaProvider(moduleType, lg, metafullId, this.bookConfig);
            if (metaProvider == null)
                throw new ApplicationException("Unable to get meta provider for: " + metafullId);

            switch (moduleType)
            {
                case TableOfContents.Overview:
                    GenerateOverview(metaProvider, metafullId);
                    doc.NewPage();
                    break;
                case TableOfContents.Catalog:
                    GenerateCatVariables(metaProvider, metafullId);
                    doc.NewPage();
                    break;
                case TableOfContents.Questionnaires:
                    GenerateQuest(metaProvider, metafullId);
                    doc.NewPage();
                    break;
                case TableOfContents.DataTables:
                    break;
                case TableOfContents.Codelists:
                    GenerateCodelists(metaProvider, metafullId);
                    break;
                case TableOfContents.Correspondences:
                    break;
                case TableOfContents.Hierarchies:
                    GenerateHierarchies(metaProvider, metafullId);
                    doc.NewPage();
                    break;
                default:
                    break;
            }
        }

        private void GenerateOverview(IMetaProvider metaProvider, string metaFullId)
        {
            hasImpressum = true;
            ImpressumModule impressumModule = new ImpressumModule(metaProvider, doc, Fonts, bookConfig);
            PdfPTable tbImpressum = impressumModule.Tables[0];
            tbImpressum.HorizontalAlignment = Element.ALIGN_LEFT;
            impressumModule.ImpressumResize(tbImpressum);
            doc.Add(tbImpressum);
            doc.NewPage();

            OverViewModule ov = new OverViewModule(metaProvider, doc, Fonts, bookConfig);
            for (int i = 0; i < ov.Tables.Count; i++)
            {
                if (i == 0)
                    AddToc(ov.tables[0]);

                doc.Add(ov.Tables[i]);
                doc.Add(new Paragraph("\n"));
                if (i == 0)
                    doc.Add(new Paragraph("\n"));

            }
        }
        private void GenerateQuest(IMetaProvider metaProvider, string metaFullId)
        {
            QuestionnaireModule module = new QuestionnaireModule(metaProvider, doc, Fonts, bookConfig);

            for (int i = 0; i < module.Tables.Count; i++)
            {
                if (i == 0)
                    AddToc(module.tables[0]);

                doc.Add(module.Tables[i]);
                doc.Add(new Paragraph("\n"));
            }
        }

        private void GenerateCatVariables(IMetaProvider metaProvider, string csFullId)
        {
            CatVarModule ov = new CatVarModule(metaProvider, doc, Fonts, bookConfig);

            for (int i = 0; i < ov.Tables.Count; i++)
            {
                //ov.Tables[i].HorizontalAlignment = Element.ALIGN_LEFT;
               // ov.Tables[i].TotalWidth = doc.PageSize.Width;
                if (i == 0)
                    AddToc(ov.tables[0]);
                doc.Add(ov.Tables[i]);
                doc.Add(new Paragraph("\n"));

            }
        }

        private void GenerateHierarchies(IMetaProvider metaProvider, string fullId)
        {
            HierarchyCodelistModule module = new HierarchyCodelistModule(metaProvider, doc, Fonts, bookConfig);

            for (int i = 0; i < module.Tables.Count; i++)
            {
                if (i == 0)
                    AddToc(module.tables[0]);

                doc.Add(module.Tables[i]);
                doc.Add(new Paragraph("\n"));
            }
        }
        private void GenerateCodelists(IMetaProvider metaProvider, string fullId)
        {
            CodelistModule module = new CodelistModule(metaProvider, doc, Fonts, bookConfig);

            for (int i = 0; i < module.Tables.Count; i++)
            {
                doc.Add(module.Tables[i]);
                doc.Add(new Paragraph("\n"));
            }
        }

        /// <summary>Uses title table of module to create an entry in table of content</summary>
        /// <param name="table">module 1st table</param>
        private void AddToc(PdfBookPTable ptb)
        {
            PdfPCell[] cells = ptb.Table.Rows[0].GetCells();
            Phrase ph = cells[0].Phrase;
            contentsTable.Add(new TableOfContentsEntry(ph.Content, docWriter.CurrentPageNumber.ToString()));
        }

        public void CreateTableOfContents()
        {
            doc.NewPage();

            BaseModule bm = new BaseModule(null, doc, Fonts, bookConfig);
            PdfPTable table = bm.TableTitle(bookConfig.TableOfContentName[lg]);
            doc.Add(table);
            doc.Add(new Chunk(Environment.NewLine));

            PdfPTable pdfContentsTable = new PdfPTable(2);
            float[] widths = new float[] { 80f, 20f };
            pdfContentsTable.SetWidths(widths);
            pdfContentsTable.TotalWidth = doc.PageSize.Width - doc.LeftMargin - doc.RightMargin; ;

            foreach (TableOfContentsEntry content in contentsTable)
            {

                Phrase ph = new Phrase(content.Title);
                PdfPCell nameCell = new PdfPCell(pdfContentsTable);
                nameCell.Border = Rectangle.NO_BORDER;
                nameCell.PaddingLeft = 60f;
                nameCell.Phrase = new Phrase(content.Title, Fonts.FontLevelTwo);
                pdfContentsTable.AddCell(nameCell);

                ph = new Phrase(content.Page, Fonts.FontLevelTwo);
                Anchor click = new Anchor(ph);
                click.Reference = "#" + content.Title;

                PdfPCell pageCell = new PdfPCell(click);
                pageCell.Border = Rectangle.NO_BORDER;
                pageCell.Padding = 6f;

                pdfContentsTable.AddCell(pageCell);
            }

            doc.Add(pdfContentsTable);
            doc.Add(new Chunk(Environment.NewLine));

            /** Reorder pages so that TOC will will be the second page in the doc
            * right after the title page**/
            int toc = docWriter.PageNumber - 1;
            int total = docWriter.ReorderPages(null);

            if (total > 2)
            {
                int[] order = new int[total];

                if (hasImpressum)
                {
                    order[0] = 1;
                    order[1] = 2;
                    order[2] = toc;
                    for (int i = 3; i < total; i++)
                    {
                        order[i] = i;
                    }
                }
                else
                {
                    order[0] = 1;
                    order[1] = toc;
                    for (int i = 2; i < total; i++)
                    {
                        order[i] = i;
                    }
                }

                docWriter.ReorderPages(order);
            }
        }
    }




    public class PDFFooterHelper : PdfPageEventHelper
    {
        private string _docBaseName;
        private Document doc;
        private PdfFonts BookFonts;
        private PdfWriter docWriter;
        // This is the contentbyte object of the writer
        PdfContentByte cb;
        private BookConfiguration bookConfig;

        // we will put the final number of pages in a template
        PdfTemplate template;

        // this is the BaseFont we are going to use for the header / footer
        BaseFont bf = null;

        public Font FooterFont { get; set; }

        public PDFFooterHelper(string docBaseName, PdfFonts fonts, BookConfiguration cfg)
        {
            _docBaseName = docBaseName;
            BookFonts = fonts;
            bookConfig = cfg;
        }

        public override void OnOpenDocument(PdfWriter writer, Document document)
        {
            docWriter = writer;
            doc = document;
            bf = BaseFont.CreateFont(BaseFont.HELVETICA, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
            cb = writer.DirectContent;
            template = cb.CreateTemplate(50, 50);
        }

        public override void OnEndPage(PdfWriter writer, Document document)
        {
            base.OnEndPage(writer, document);

            if (document.PageNumber > 2 && !BookGenerator.InTOC)
            {

                PdfPTable tbFooter = FooterTable(_docBaseName, document.PageNumber - 2);
                tbFooter.WriteSelectedRows(0, -1, bookConfig.Margins.Left, bookConfig.Margins.Top, docWriter.DirectContent);
            }
        }

        public PdfPTable FooterTable(string title, int pageNr)
        {
            PdfPTable table = new PdfPTable(2);
            float[] widths = new float[] { 50f, 50f };
            table.TotalWidth = doc.PageSize.Width - doc.LeftMargin - doc.RightMargin;


            // We invert text and page number according to page generated
            string left = pageNr % 2 == 0 ? pageNr.ToString() : title;
            string right = pageNr % 2 == 0 ? title : pageNr.ToString();
            Font leftFont = pageNr % 2 == 0 ? BookFonts.FontImpressumTitle : BookFonts.FontImpressumValue;
            Font rightFont = pageNr % 2 == 0 ? BookFonts.FontImpressumValue : BookFonts.FontImpressumTitle;

            // 1st cell is number if even, else user defined bottom text
            Phrase p = new Phrase(left, leftFont);
            PdfPCell cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            table.AddCell(cell);

            // 2nd cell is number if off, else user defined bottom text
            p = new Phrase(right, rightFont);
            cell = new PdfPCell(p);
            cell.Border = Rectangle.NO_BORDER;
            cell.HorizontalAlignment = Element.ALIGN_RIGHT;
            table.AddCell(cell);

            return table;
        }
    }
}
